﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Runtime.Serialization;
using PSE.Framework.Serialization;
using PSE.Framework.UI.Security.Authentication;
using PSE.Framework.ProviderLoader.Configuration;
using PSE.Framework.Configuration;
using PSE.Framework.ProviderLoader;
using PSE.Framework.ErrorLogging;

namespace PSE.Framework.UI.Settings.Providers
{
    /// <summary>
    /// Persite as configurações no diretório do usuário corrente da máquina
    /// </summary>
    public class LocalConfigurationSettings : BaseProvider, IConfigurationSettings<ConfigurationObjectType>
    {
        private const string FILE_PATH = @"\{0}\Settings\";

        private const string NODE_NAME = "ConfigurationName";

        private const string TAG_LAYOUT_SETTINGS = "LayoutSettings";

        public LocalConfigurationSettings(ProviderConfigurationElement configuration)
            : base(configuration)
        {
        }

        /// <summary>
        /// Indica quando o Provider está usando o modo Begin / End Save
        /// </summary>
        private bool _usingBeginSaveOrLoadOperation = false;

        /// <summary>
        /// Guarda informações de XmlDocument para os casos onde está sendo usado o modo Begin / End Save
        /// </summary>
        private struct XmlDiskDocumentInfo
        {
            public XmlDocument xmlDiskDocument;
            public string xmlPath;
            public string xmlName;
            public string path;
        }

        private XmlDiskDocumentInfo _xmlDiskDocumentInfo;


        private string GetSettingsPath(IConfigurationObject<ConfigurationObjectType> configuration)
        {
            ProductConfigurationSection configSection = ApplicationConfiguration.GetCurrent().GetSection<ProductConfigurationSection>("productConfiguration");
            string path = string.Format(@"{0}{1}", System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), string.Format(FILE_PATH, configSection.ProductName));
            path += configuration.ConfigurationType.ToString() + @"\";
            return path;
        }

        /// <summary>
        /// Obtém um Node em XmlDocument, recebendo o nome da tag para este Node (será o nome do tipo que define a tag),
        /// e o nome atribuído na SubTag ConfigurationName (definida por IConfigurationObject).
        /// A busca é feita no primeiro nível de DocumentElement
        /// </summary>
        /// <param name="doc">XmlDocument onde buscar o Node</param>
        /// <param name="nodeName">Nome da tag que define o Node, que será o nome do tipo (ex: ExpanderConfiguration) </param>
        /// <param name="configurationName">Nome da configuração, que aparece dentro da Tag ConfigurationName (definida no 
        /// IConfigurationObject que é serializado)</param>
        /// <returns>XmlNode procurado, caso encontre-o. Se não encontrá-lo, retorna null</returns>
        private XmlNode GetFirstLevelXmlNodeByName(XmlDocument doc, string nodeName, string configurationName)
        {
            foreach (XmlNode node in doc.DocumentElement.ChildNodes)
            {
                if (node.Name.ToUpper() == nodeName.ToUpper())
                {
                    foreach (XmlNode subNode in node.ChildNodes)
                    {
                        if (subNode.Name.ToUpper() == NODE_NAME.ToUpper())
                        {
                            if (subNode.InnerXml.ToUpper() == configurationName.ToUpper())
                            {
                                return node;
                            }
                        }
                    }
                }
            }
            return null;
        }


        /// <summary>
        /// Cria um XmlDocument básico, com XmlDeclaration já preenchida e DocumentElement vazio
        /// </summary>
        /// <returns>XmlDocument com XmlDeclaration já preenchida e DocumentElement vazio</returns>
        private XmlDocument GetBaseXml()
        {
            XmlDocument doc = new XmlDocument();
            XmlDeclaration dec = doc.CreateXmlDeclaration("1.0", "utf-8", null);
            doc.AppendChild(dec);
            return doc;
        }

        /// <summary>
        /// Cria um XmlDocument para configuração de layout, que é um XmlDocument com declaration já preenchida
        /// e com DocumentElement de configuração de layout (sem nós filhos)
        /// </summary>
        /// <returns>XmlDocument com declaration já preenchida e com DocumentElement de configuração de layout (sem nós filhos)</returns>
        private XmlDocument GetBaseLayoutSettingsXml()
        {
            XmlDocument doc = GetBaseXml();
            XmlElement element = doc.CreateElement(TAG_LAYOUT_SETTINGS);
            doc.AppendChild(element);
            return doc;
        }

        /// <summary>
        /// Converte um XmlDocument para um XmlDocument com configuração de layout.
        /// </summary>
        /// <param name="baseXml">XmlDocument a ser convertido</param>
        /// <returns>XmlDocument ño formato para configuração de layout</returns>
        private XmlDocument ConvertXmlToLayoutSettingsXml(XmlDocument baseXml)
        {
            XmlDocument doc = GetBaseLayoutSettingsXml();
            XmlNode node = doc.ImportNode(baseXml.DocumentElement, true);
            doc.DocumentElement.AppendChild(node);
            return doc;
        }

        /// <summary>
        /// Converte um XmlDocument com configuração de layout para um XmlDocument simples
        /// </summary>
        /// <param name="layoutXml">XmlDocument a ser convertido (deve estar no padrão de configuração de layout)</param>
        /// <param name="nodeName">Nome do node (tag) a ser usado na conversão</param>
        /// <param name="configurationName">ConfigurationName do node a ser usado na conversão</param>
        /// <returns>XmlDocument no formato simples</returns>
        private XmlDocument ConvertLayoutSettingsXmlToXml(XmlDocument layoutXml, string nodeName, string configurationName)
        {
            XmlNode nodeToCopy = GetFirstLevelXmlNodeByName(layoutXml, nodeName, configurationName);
            if (nodeToCopy == null)
            {
                //throw new XmlException("Node " + nodeName + " with configuratioName = " + configurationName + " not found.");


                return null;
            }
            XmlDocument doc = GetBaseXml();
            XmlNode node = doc.ImportNode(nodeToCopy, true);
            doc.AppendChild(node);
            return doc;
        }


        /// <summary>
        /// Retorna XML (como XmlDocument) de um determinado IConfigurationObject
        /// </summary>
        /// <param name="configuration">Objeto para o qual se deseja carregar o XmlDocument</param>
        /// <returns>
        ///    XmlDocument montado de acordo com configuração salva de IConfigurationObject. 
        ///    Retorna null se a configuração salva não existir.
        /// </returns>
        private XmlDocument LoadXml(string xmlPath, string xmlName)
        {
            if (String.IsNullOrEmpty(xmlPath))
                throw new ArgumentException("xmlPath", "Xml path cannot be null or empty.");
            if (String.IsNullOrEmpty(xmlName))
                throw new ArgumentException("xmlName", "Xml name cannot be null or empty.");

            XmlDocument xmlDocument = new XmlDocument();
            string path = string.Format(@"{0}{1}.xml", xmlPath, xmlName);

            if (System.IO.File.Exists(path))
            {
                // Evita acesso ao mesmo arquivo se a janela estiver com mais de uma instância aberta
                lock (this)
                {
                    xmlDocument.Load(path);
                }

                return xmlDocument;
            }

            return null;
        }

        /// <summary>
        /// Apaga um determinado XML Node do arquivo de configuração.
        /// A remoção é aplicada quando um node gera erro ao carregar, porque o IConfigurationObject correspondente
        /// teve alterações que tornam o xml gravado incompatível (por exemplo, se mudou uma propriedade que era double para int,
        /// um valor 10.2 no xml irá causar erro no deserialize)
        /// </summary>
        /// <param name="xmlPath">Caminho do XML de configuração de layout</param>
        /// <param name="xmlName">Nome do XML de configuração de layout</param>
        /// <param name="nodeToDeleteName">Nome do node a ser removido</param>
        /// <param name="nodeToDeleteConfigName">ConfigurationName do node a ser removido</param>
        /// <returns>tyrue se removeu node, false se não conseguiu (por exemplo, node não existia no arquivo</returns>
        private bool DeleteXmlNode(string xmlPath, string xmlName, string nodeToDeleteName, string nodeToDeleteConfigName)
        {

            XmlDocument xmlSaved = null;
            bool salvar = false;

            try
            {
                if (_usingBeginSaveOrLoadOperation)
                {
                    xmlSaved = _xmlDiskDocumentInfo.xmlDiskDocument;
                }
                else
                {
                    // Carrega xml salvo
                    xmlSaved = LoadXml(xmlPath, xmlName);
                }

                if (xmlSaved != null)
                {
                    XmlNode nodeToDelete = GetFirstLevelXmlNodeByName(xmlSaved, nodeToDeleteName, nodeToDeleteConfigName);
                    if (xmlSaved.DocumentElement.RemoveChild(nodeToDelete) != null)
                        salvar = true;
                }

            }
            catch (Exception ex)
            {
                throw new Exception("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: An error occurred while trying to serialize.", ex);
            }

            if (salvar)
            {
                if (_usingBeginSaveOrLoadOperation)
                {
                    _xmlDiskDocumentInfo.xmlDiskDocument = xmlSaved;
                    return true;
                }
                else
                {
                    try
                    {

                        string path = string.Format(@"{0}{1}.xml", xmlPath, xmlName);

                        // Verifica se existe a pasta
                        if (!System.IO.Directory.Exists(xmlPath))
                            System.IO.Directory.CreateDirectory(xmlPath);

                        // Evita acesso ao mesmo arquivo se a janela estiver com mais de uma instância aberta
                        lock (this)
                        {
                            xmlSaved.Save(path);
                        }
                        return true;

                    }
                    catch (XmlException ex)
                    {
                        //SystemErrorLog.Log(ex, "PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The xml configuration file was not well formed.");
                        throw new XmlException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The xml configuration file was not well formed.", ex);
                    }
                    catch (UnauthorizedAccessException ex)
                    {
                        //SystemErrorLog.Log(ex, "PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The caller does not have the required permission to save xml configuration file.");
                        throw new UnauthorizedAccessException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The caller does not have the required permission to save xml configuration file.", ex);
                    }
                    catch (ArgumentNullException ex)
                    {
                        //SystemErrorLog.Log(ex, "PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The Path to save xml configuration file is null reference.");
                        throw new ArgumentNullException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The Path to save xml configuration file is null reference.", ex);
                    }
                    catch (ArgumentException ex)
                    {
                        //SystemErrorLog.Log(ex, "PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The path to save xml configuration file is a zero-length string, contains only white space, or contains one or more invalid characters as defined by InvalidPathChars. path is prefixed with, or contains only a colon character (:).");
                        throw new ArgumentException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The path to save xml configuration file is a zero-length string, contains only white space, or contains one or more invalid characters as defined by InvalidPathChars. path is prefixed with, or contains only a colon character (:).", ex);
                    }
                    catch (PathTooLongException ex)
                    {
                        //SystemErrorLog.Log(ex, "PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The specified path, file name, or both that save the xml configuration file exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters and file names must be less than 260 characters.");
                        throw new PathTooLongException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The specified path, file name, or both that save the xml configuration file exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters and file names must be less than 260 characters.", ex);
                    }
                    catch (DirectoryNotFoundException ex)
                    {
                        //SystemErrorLog.Log(ex, "PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The specified directory to save xml configuration file does not exists.");
                        throw new DirectoryNotFoundException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The specified directory to save xml configuration file does not exists.", ex);
                    }
                    catch (NotSupportedException ex)
                    {
                        //SystemErrorLog.Log(ex, "PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The path to save xml configuration file contains a colon character (:) that is not part of a drive label (C:\\).");
                        throw new NotSupportedException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The path to save xml configuration file contains a colon character (:) that is not part of a drive label (C:\\).", ex);
                    }
                    catch (IOException ex)
                    {
                        //SystemErrorLog.Log(ex, "PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The directory specified by path to save xml configuration file is read-only.");
                        throw new IOException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: The directory specified by path to save xml configuration file is read-only.", ex);
                    }
                    catch (Exception ex)
                    {
                        //SystemErrorLog.Log(ex, "PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: An error occurred while trying to save xml configuration file.");
                        throw new Exception("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.DeleteXmlNode: An error occurred while trying to save xml configuration file.", ex);
                    }
                }
            }
            else
            {
                return false;
            }
        }


        /// <summary>
        /// Recupera o objeto de configuração
        /// </summary>
        /// <param name="configuration">Objeto a ser recuperado</param>
        /// <param name="parentConfiguration">Objeto "pai", que define o xml dentro do qual está o configuration a ser carregado</param>
        /// <returns>true se conseguir recuperar o objeto com sucesso, false se não existir objeto a ser recuperado</returns>
        public bool Load(ref IConfigurationObject<ConfigurationObjectType> configuration)
        {
            return Load(ref configuration, null);
        }

        /// <summary>
        /// Recupera o objeto de configuração. Sobrecarga para quando não há configuração "pai".
        /// </summary>
        /// <param name="configuration">Objeto a ser recuperado</param>
        /// <returns>true se conseguir recuperar o objeto com sucesso, false se não existir objeto a ser recuperado</returns>
        public bool Load(ref IConfigurationObject<ConfigurationObjectType> configuration, IConfigurationObject<ConfigurationObjectType> parentConfiguration)
        {
            XmlDocument xmlDocument = null;
            XmlDocument xmlSaved = null;

            string xmlPath;
            string xmlName;
            string nodeName;
            string nodeConfigurationName;

            // Obtém path e nome do xml
            // Esses valores dependem de haver ou não o parentConfiguration

            if (_usingBeginSaveOrLoadOperation)
            {
                xmlSaved = _xmlDiskDocumentInfo.xmlDiskDocument;
                xmlPath = _xmlDiskDocumentInfo.xmlPath;
                xmlName = _xmlDiskDocumentInfo.xmlName;
            }
            else
            {

                if (parentConfiguration == null)
                {
                    xmlPath = GetSettingsPath(configuration);
                    xmlName = configuration.ConfigurationName;
                }
                else
                {
                    xmlPath = GetSettingsPath(parentConfiguration);
                    xmlName = parentConfiguration.ConfigurationName;
                }

                // Carrega xml salvo
                xmlSaved = LoadXml(xmlPath, xmlName);
            }

            // Obtém nome do node e seu ConfigurationName (definido em IConfigurationObject)
            nodeName = configuration.GetType().Name;
            nodeConfigurationName = configuration.ConfigurationName;


            if (xmlSaved != null)
            {
                xmlDocument = ConvertLayoutSettingsXmlToXml(xmlSaved, nodeName, nodeConfigurationName); // GetFirstLevelXmlNodeAsXmlDocument(xmlSaved, nodeName, nodeConfigurationName);   
            }

            if (xmlDocument != null)
            {
                try
                {
                    XmlSerializer serializer = XmlSerializer.GetInstance();
                    configuration = (IConfigurationObject<ConfigurationObjectType>)serializer.Deserialize(xmlDocument, configuration.GetType());
                }
                catch (Exception ex)
                {
                    LogData logData = ErrorLogger.WriteLog(LogType.Error, new Exception(string.Format("Could not deserialize layout configuration due to an exception. Node '{0}' (ConfigurationName = '{1}') will be deleted.", nodeName, nodeConfigurationName), ex));

                    DeleteXmlNode(xmlPath, xmlName, nodeName, nodeConfigurationName);
                }

                return true;
            }
            else
            {
                return false;
            }
        }


        /// <summary>
        /// Persiste o objeto de configuração. Sobrecarga para quando não há configuração "pai".
        /// </summary>
        /// <param name="configuration">Objeto a ser salvo</param>
        /// <returns>true se conseguir salvar o objeto com sucesso, false se não conseguir</returns>
        public bool Save(IConfigurationObject<ConfigurationObjectType> configuration)
        {
            return Save(configuration, null);
        }


        /// <summary>
        /// Persiste o objeto de configuração
        /// </summary>
        /// <param name="configuration">Objeto a ser salvo</param>
        /// <param name="parentConfiguration">Objeto "pai", que define o xml dentro do qual configuration deve ser salvo</param>
        /// <returns>true se conseguir salvar o objeto com sucesso, false se não conseguir</returns>
        public bool Save(IConfigurationObject<ConfigurationObjectType> configuration, IConfigurationObject<ConfigurationObjectType> parentConfiguration)
        {
            XmlSerializer serializer = null;
            XmlDocument xmlDocument = null;

            XmlDocument xmlToSave = null;
            XmlDocument xmlSaved = null;

            string xmlPath;
            string xmlName;
            string path;

            string nodeName;
            string nodeConfigurationName;

            if (configuration == null)
                throw new ArgumentNullException("configuration");
            if (string.IsNullOrEmpty(configuration.ConfigurationName))
                throw new ArgumentNullException("ConfigurationName", "ConfigurationName cannot be null");

            try
            {
                if (_usingBeginSaveOrLoadOperation)
                {
                    xmlSaved = _xmlDiskDocumentInfo.xmlDiskDocument;
                    xmlPath = _xmlDiskDocumentInfo.xmlPath;
                    xmlName = _xmlDiskDocumentInfo.xmlName;
                    path = _xmlDiskDocumentInfo.path;
                }
                else
                {
                    // Obtém path e nome do xml
                    // Esses valores dependem de haver ou não o parentConfiguration
                    if (parentConfiguration == null)
                    {
                        xmlPath = GetSettingsPath(configuration);
                        xmlName = configuration.ConfigurationName;
                    }
                    else
                    {
                        xmlPath = GetSettingsPath(parentConfiguration);
                        xmlName = parentConfiguration.ConfigurationName;
                    }

                    path = this.GetSettingsPath(configuration);

                    // Obtém arquivo xml já salvo, caso ele exista
                    xmlSaved = LoadXml(xmlPath, xmlName);
                }

                // Obtém nome do node e seu ConfigurationName (definido em IConfigurationObject)
                nodeName = configuration.GetType().Name;
                nodeConfigurationName = configuration.ConfigurationName;

                // Obtém Xml para o configuration a ser salvo

                serializer = XmlSerializer.GetInstance();

                xmlDocument = serializer.Serialize(configuration);
                // Converte o xmlDocument para formato layout settings
                xmlDocument = ConvertXmlToLayoutSettingsXml(xmlDocument);

                if (xmlSaved != null)
                {
                    // Já existe xml salvo, utiliza ele
                    XmlNode nodeUpdateNew = GetFirstLevelXmlNodeByName(xmlDocument, nodeName, nodeConfigurationName);
                    nodeUpdateNew = xmlSaved.ImportNode(nodeUpdateNew, true);
                    XmlNode nodeUpdateOld = GetFirstLevelXmlNodeByName(xmlSaved, nodeName, nodeConfigurationName);

                    // O novo node deve existir em xmlDocument
                    if (nodeUpdateNew == null)
                        throw new XmlException("Node to insert " + nodeName + " new configuration not found in configuration object");

                    if (nodeUpdateOld == null)
                    {
                        // Node ainda não existe no xml, deve ser adicionado de acordo com xmlDocument
                        XmlNode nodeToInsert = xmlSaved.ImportNode(nodeUpdateNew, true);
                        xmlSaved.DocumentElement.AppendChild(nodeToInsert);
                    }
                    else
                    {
                        // Node já existe no xml, deve ser atualizado de acordo com xmlDocument
                        xmlSaved.DocumentElement.ReplaceChild(nodeUpdateNew, nodeUpdateOld);
                    }
                    // Salva xml existente, após alterá-lo para as novas configurações
                    xmlToSave = xmlSaved;

                }
                else
                {
                    // Não há xml salvo, então o xmlDocumento serializado será salvo
                    xmlToSave = xmlDocument;
                }
            }
            catch (SerializationException ex)
            {
                throw new SerializationException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.Save: An serialization error occurred while trying to serialize.", ex);
            }
            catch (XmlException ex)
            {
                throw new XmlException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.Save: The xml configuration file was not well formed.", ex);
            }
            catch (Exception ex)
            {
                throw new Exception("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.Save: An error occurred while trying to serialize.", ex);
            }

            if (xmlDocument == null)
                return false;


            if (_usingBeginSaveOrLoadOperation)
            {
                _xmlDiskDocumentInfo.xmlDiskDocument = xmlToSave;
            }
            else
            {
                try
                {
                    // Verifica se existe a pasta
                    if (!System.IO.Directory.Exists(path))
                        System.IO.Directory.CreateDirectory(path);

                    // Evita acesso ao mesmo arquivo se a janela estiver com mais de uma instância aberta
                    lock (this)
                    {
                        xmlToSave.Save(string.Format(@"{0}{1}.xml", xmlPath, xmlName));
                    }

                }
                catch (XmlException ex)
                {
                    throw new XmlException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.Save: The xml configuration file was not well formed.", ex);
                }
                catch (UnauthorizedAccessException ex)
                {
                    throw new UnauthorizedAccessException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.Save: The caller does not have the required permission to save xml configuration file.", ex);
                }
                catch (ArgumentNullException ex)
                {
                    throw new ArgumentNullException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.Save: The Path to save xml configuration file is null reference.", ex);
                }
                catch (ArgumentException ex)
                {
                    throw new ArgumentException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.Save: The path to save xml configuration file is a zero-length string, contains only white space, or contains one or more invalid characters as defined by InvalidPathChars. path is prefixed with, or contains only a colon character (:).", ex);
                }
                catch (PathTooLongException ex)
                {
                    throw new PathTooLongException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.Save: The specified path, file name, or both that save the xml configuration file exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters and file names must be less than 260 characters.", ex);
                }
                catch (DirectoryNotFoundException ex)
                {
                    throw new DirectoryNotFoundException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.Save: The specified directory to save xml configuration file does not exists.", ex);
                }
                catch (NotSupportedException ex)
                {
                    throw new NotSupportedException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.Save: The path to save xml configuration file contains a colon character (:) that is not part of a drive label (C:\\).", ex);
                }
                catch (IOException ex)
                {
                    throw new IOException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.Save: The directory specified by path to save xml configuration file is read-only.", ex);
                }
                catch (Exception ex)
                {
                    throw new Exception("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.Save: An error occurred while trying to save xml configuration file.", ex);
                }
            }

            return true;
        }


        #region IConfigurationSettings Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="parentConfiguration"></param>
        /// <returns></returns>
        public void BeginSaveOrLoadOperation(IConfigurationObject<ConfigurationObjectType> parentConfiguration)
        {
            if (!_usingBeginSaveOrLoadOperation)
            {

                _usingBeginSaveOrLoadOperation = true;

                // Obtém path e nome do xml
                // Esses valores dependem de haver ou não o parentConfiguration
                if (parentConfiguration != null)
                {
                    _xmlDiskDocumentInfo.xmlPath = GetSettingsPath(parentConfiguration);
                    _xmlDiskDocumentInfo.xmlName = parentConfiguration.ConfigurationName;
                    _xmlDiskDocumentInfo.path = this.GetSettingsPath(parentConfiguration);
                    // Carrega xml salvo
                    _xmlDiskDocumentInfo.xmlDiskDocument = LoadXml(_xmlDiskDocumentInfo.xmlPath, _xmlDiskDocumentInfo.xmlName);
                }
                else
                {
                    throw new Exception("ParentConfiguration cannot be null");
                }
            }
            else
            {
                throw new Exception("BeginSaveOrLoadOperation already active");
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public void EndSaveOrLoadOperation()
        {
            if (_usingBeginSaveOrLoadOperation)
            {

                if (_xmlDiskDocumentInfo.xmlDiskDocument != null)
                {
                    try
                    {

                        // Verifica se existe a pasta
                        if (!System.IO.Directory.Exists(_xmlDiskDocumentInfo.path))
                            System.IO.Directory.CreateDirectory(_xmlDiskDocumentInfo.path);

                        // Evita acesso ao mesmo arquivo se a janela estiver com mais de uma instância aberta
                        lock (this)
                        {
                            _xmlDiskDocumentInfo.xmlDiskDocument.Save(string.Format(@"{0}{1}.xml", _xmlDiskDocumentInfo.xmlPath, _xmlDiskDocumentInfo.xmlName));
                        }

                    }
                    catch (XmlException ex)
                    {
                        throw new XmlException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.EndSaveOrLoadOperation: The xml configuration file was not well formed.", ex);
                    }
                    catch (UnauthorizedAccessException ex)
                    {
                        throw new UnauthorizedAccessException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.EndSaveOrLoadOperation: The caller does not have the required permission to save xml configuration file.", ex);
                    }
                    catch (ArgumentNullException ex)
                    {
                        throw new ArgumentNullException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.EndSaveOrLoadOperation: The Path to save xml configuration file is null reference.", ex);
                    }
                    catch (ArgumentException ex)
                    {
                        throw new ArgumentException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.EndSaveOrLoadOperation: The path to save xml configuration file is a zero-length string, contains only white space, or contains one or more invalid characters as defined by InvalidPathChars. path is prefixed with, or contains only a colon character (:).", ex);
                    }
                    catch (PathTooLongException ex)
                    {
                        throw new PathTooLongException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.EndSaveOrLoadOperation: The specified path, file name, or both that save the xml configuration file exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters and file names must be less than 260 characters.", ex);
                    }
                    catch (DirectoryNotFoundException ex)
                    {
                        throw new DirectoryNotFoundException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.EndSaveOrLoadOperation: The specified directory to save xml configuration file does not exists.", ex);
                    }
                    catch (NotSupportedException ex)
                    {
                        throw new NotSupportedException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.EndSaveOrLoadOperation: The path to save xml configuration file contains a colon character (:) that is not part of a drive label (C:\\).", ex);
                    }
                    catch (IOException ex)
                    {
                        throw new IOException("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.EndSaveOrLoadOperation: The directory specified by path to save xml configuration file is read-only.", ex);
                    }
                    catch (Exception ex)
                    {
                        throw new Exception("PSE.Framework.UI.Settings.Providers.LocalConfigurationSettings.EndSaveOrLoadOperation: An error occurred while trying to save xml configuration file.", ex);
                    }
                    finally
                    {
                        _xmlDiskDocumentInfo.xmlDiskDocument = null;
                        _xmlDiskDocumentInfo.xmlName = String.Empty;
                        _xmlDiskDocumentInfo.xmlPath = String.Empty;
                        _xmlDiskDocumentInfo.path = String.Empty;
                        _usingBeginSaveOrLoadOperation = false;
                    }
                }

                _usingBeginSaveOrLoadOperation = false;
            }
            else
            {
                throw new Exception("BeginSaveOrLoadOperation must be called before an EndSaveOrLoadOperation");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public void CancelSaveOrLoadOperation()
        {
            _xmlDiskDocumentInfo.xmlDiskDocument = null;
            _xmlDiskDocumentInfo.xmlName = String.Empty;
            _xmlDiskDocumentInfo.xmlPath = String.Empty;
            _xmlDiskDocumentInfo.path = String.Empty;
            _usingBeginSaveOrLoadOperation = false;
        }

        #endregion

    }
}
